#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <cmath>

#include "shaders.hpp"
#include "renderer.hpp"

Color Shader::shade(const SampleInfo& sample, const Scene& scene, const Renderer& renderer) const
{
	throw runtime_error("Virtual interface function ""shade"" must be overloaded!");
}

ostream& operator<<(ostream& os, const Shader& dt)
{
	cout << "<" << setbase(16) << (unsigned __int64)&dt << setbase(10) << ">" ;
	return os;
}

DebugShader::DebugShader()
{
}

Color DebugShader::shade(const SampleInfo& sample, const Scene& scene, const Renderer& renderer) const
{
/*	cout << "ds:";
	printSample(&sample);
	printChain(sample.prevSample);
	cout << endl << endl;*/

	const SurfacePrimitive* prim = static_cast<const SurfacePrimitive*>(sample.prim);
	Vector3 uvw = prim->getUVW(sample);
	return Color((uvw.x * 5) - floor(uvw.x * 5), (uvw.y * 5) - floor(uvw.y * 5), (uvw.z * 5) - floor(uvw.z * 5));
}

const char* DebugShader::getType()
{
	return "debug";
}

void DebugShader::printSample(const SampleInfo* sample) const
{
	if(sample->miscInt == Primitive::FRONT_INTERSECTION)
		cout << "fi";
	else
		cout << "bi";
	cout << sample->position << sample->prim->getType() << setbase(16) << (unsigned __int64)sample->prim << setbase(10);
}

void DebugShader::printChain(const SampleInfo* sample) const
{
	if(sample == NULL)
		return;
	else
	{
		cout << "-";
		printSample(sample);
		if(sample == sample->prevSample)
		{
			cout << "hey, stop!";
			return;
		}
		printChain(sample->prevSample);
	}
}

ConstShader::ConstShader(Color color)
{
	color_ = color;
}

Color ConstShader::shade(const SampleInfo& sample, const Scene& scene, const Renderer& renderer) const
{
	return color_;
}

const char* ConstShader::getType()
{
	return "const";
}

IncidenceShader::IncidenceShader(Color frontColor, Color sideColor)
{
	frontColor_ = frontColor;
	sideColor_ = sideColor;
}

Color IncidenceShader::shade(const SampleInfo& sample, const Scene& scene, const Renderer& renderer) const
{
	const SurfacePrimitive* prim = static_cast<const SurfacePrimitive*>(sample.prim);
	double k = fabs(dot(sample.ray.getDirection(), prim->getNormal(sample, SurfacePrimitive::RAW_NORMAL)));
	return frontColor_ * k + sideColor_ * (1.0 - k);
}

const char* IncidenceShader::getType()
{
	return "incidence";
}

OcclusionShader::OcclusionShader(Color fade) : fade_(fade)
{
}

Color OcclusionShader::shade(const SampleInfo& sample, const Scene& scene, const Renderer& renderer) const
{
	const SurfacePrimitive* prim = static_cast<const SurfacePrimitive*>(sample.prim);
	while(true)
	{
		Vector3 dir(((double)rand() / (double) RAND_MAX) - 0.5, ((double)rand() / (double) RAND_MAX) - 0.5, ((double)rand() / (double) RAND_MAX) - 0.5);
		dir = dir * 2.0;
		double k = dot(prim->getNormal(sample, SurfacePrimitive::RAW_NORMAL), dir);
		if(k > 0.0 && mod(dir) < 1.0)
			return renderer.raytrace(Ray(sample.position, dir), sample, scene) * fade_;
	}
}

const char* OcclusionShader::getType()
{
	return "occlusion";
}

MirrorShader::MirrorShader(Color fade) : fade_(fade)
{
}

Color MirrorShader::shade(const SampleInfo& sample, const Scene& scene, const Renderer& renderer) const
{
	const SurfacePrimitive* prim = static_cast<const SurfacePrimitive*>(sample.prim);
	Vector3 v = sample.position - sample.ray.getOrigin();
	Vector3 normal = prim->getNormal(sample, SurfacePrimitive::RAW_NORMAL);
	double d = dot(normal, v);
	Vector3 u = normal * 2 - normalize(v * d);
	return renderer.raytrace(Ray(sample.position, u), sample, scene) * fade_;
}

const char* MirrorShader::getType()
{
	return "mirror";
}
